﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace AhpilyServer
{
    /// <summary>
    /// 服务器端
    /// </summary>
    public class ServerPeer
    {
        /// <summary>
        /// 服务器端的Socket对象
        /// </summary>
        private Socket serverSocket;
        /// <summary>
        /// 限制客户端连接数量的信号量
        /// </summary>
        private Semaphore acceptSemaphore;
        /// <summary>
        /// 客户端对象的连接池
        /// </summary>
        private ClientPeerPool clientPool;
        /// <summary>
        /// 应用层
        /// </summary>
        private IApplication mApplication;

        #region API
        /// <summary>
        /// 设置应用层
        /// </summary>
        public void SetApplication(IApplication application)
        {
            mApplication = application;
        }
        /// <summary>
        /// 开启服务器
        /// </summary>
        /// <param name="port">端口号</param>
        /// <param name="maxConnections">最大连接数量</param>
        public void Start(int port, int maxConnections)
        {
            try
            {
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                acceptSemaphore = new Semaphore(maxConnections, maxConnections);

                //初始化连接池
                clientPool = new ClientPeerPool(maxConnections);
                for (int i = 0; i < maxConnections; i++)
                {
                    var tempClient = new ClientPeer();
                    tempClient.ReceiveArgs.Completed += ReceiveEventArg_Completed;
                    tempClient.OnReveiveMessageCompleted = ReceiveMessage_Completed;
                    tempClient.OnSendDisconnect = Disconnect;
                    clientPool.Enqueue(tempClient);
                }

                serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
                serverSocket.Listen(10);

                Console.WriteLine("服务器启动...");
                StartAccept(null);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
        #endregion

        #region 接收连接
        /// <summary>
        /// 开始等待客户端连接
        /// </summary>
        private void StartAccept(SocketAsyncEventArgs e)
        {
            if (e == null)
            {
                e = new SocketAsyncEventArgs();
                e.Completed += AcceptEventArg_Completed;
            }

            //返回值判断异步事件是否执行完毕
            bool result = serverSocket.AcceptAsync(e);
            //执行完毕后会触发
            if (!result)
            {
                ProcessAccept(e);
            }
        }

        /// <summary>
        /// 接收连接请求异步事件完成时触发
        /// </summary>
        private void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }

        /// <summary>
        /// 处理连接请求
        /// </summary>
        /// <param name="e"></param>
        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            //限制线程的访问
            acceptSemaphore.WaitOne();
            //得到客户端的对象
            //Socket clientSocket = args.AcceptSocket;
            ClientPeer client = clientPool.Dequeue();
            client.ClientSocket = e.AcceptSocket;
            Console.WriteLine($"客户端连接成功: {client.ClientSocket.RemoteEndPoint}");
            //开始接收数据
            StartReceive(client);

            //再次开启监听
            e.AcceptSocket = null;
            StartAccept(e);
        }
        #endregion

        #region 接收数据
        /// <summary>
        /// 开始接收数据
        /// </summary>
        private void StartReceive(ClientPeer client)
        {
            try
            {
                bool result = client.ClientSocket.ReceiveAsync(client.ReceiveArgs);
                if (!result)
                {
                    ProcessReceive(client.ReceiveArgs);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        /// <summary>
        /// 处理接收的请求
        /// </summary>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            ClientPeer client = e.UserToken as ClientPeer;
            //判断网络消息是否接收成功
            if (client.ReceiveArgs.SocketError == SocketError.Success && client.ReceiveArgs.BytesTransferred > 0)
            {
                //拷贝数据到数组中
                byte[] packet = new byte[client.ReceiveArgs.BytesTransferred];
                Buffer.BlockCopy(client.ReceiveArgs.Buffer, 0, packet, 0, packet.Length);
                //让客户端自身处理这个数据包 自身解析
                client.StartReceive(packet);
                //尾递归 持续接收请求
                StartReceive(client);
            }
            //断开连接了 如果没有传输的字节数 就代表断开连接了
            else if (client.ReceiveArgs.BytesTransferred == 0)
            {
                if (client.ReceiveArgs.SocketError == SocketError.Success)
                {
                    //客户端主动断开连接
                    Disconnect(client, "客户端主动断开连接");
                }
                else
                {
                    //由于网络异常 导致 被动断开连接
                    Disconnect(client, client.ReceiveArgs.SocketError.ToString());
                }
            }
        }

        /// <summary>
        /// 当接收完成时 触发的事件
        /// </summary>
        private void ReceiveEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessReceive(e);
        }

        /// <summary>
        /// 一条数据解析完成的处理
        /// </summary>
        /// <param name="client">对应的连接对象</param>
        /// <param name="value">解析出来的一个具体能使用的类型</param>
        private void ReceiveMessage_Completed(ClientPeer client, SocketMessage value)
        {
            mApplication.OnReceive(client, value);
        }
        #endregion

        #region 断开连接
        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="client">表示断开的客户端连接对象</param>
        /// <param name="reason">断开的原因</param>
        private void Disconnect(ClientPeer client, string reason)
        {
            try
            {
                if (client == null)
                {
                    throw new Exception("当前指定的客户端连接对象为空，无法断开连接");
                }
                Console.WriteLine($"客户端断开连接: {client.ClientSocket.RemoteEndPoint} - {reason}");
                mApplication.OnDisconnect(client);

                client.Disconnect();
                //回收对象
                clientPool.Enqueue(client);
                acceptSemaphore.Release();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
        #endregion
    }
}
